package com.oauth2.oauth2demo01.config;

import com.oauth2.oauth2demo01.execption.AuthServerExceptionHandler;
import com.oauth2.oauth2demo01.handler.CustomizationRedirectResolver;
import com.oauth2.oauth2demo01.handler.UnifiedDefaultUserAuthenticationConverter;
import com.oauth2.oauth2demo01.service.UserService;
import com.oauth2.oauth2demo01.utils.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;

/**
 * 授权服务器
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private CustomizationRedirectResolver customizationRedirectResolver;


    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserService userService;


    @Autowired
    private UnifiedDefaultUserAuthenticationConverter unifiedDefaultUserAuthenticationConverter;
    /**
     * 注册客户端，就是客户端权限访问的依据
     *
     */
    /**
     * 授权码模式:
     * request:http://localhost:9000/oauth/authorize?response_type=code&client_id=client
     * response:https://www.baidu.com/?code=Wv2lGo
     * 获取token：http://localhost:9000/oauth/token
     * authorization参数： username=client,password=111111
     * post参数：
     * grant_type=authorization_code
     * code=Wv2lGo
     * redirect_uri:http://www.baidu.com
     * 获取用户数据：
     * http://localhost:9000/index/getAuth?access_token=2dcf6d4e-c0f7-4ac1-89ce-9dd2d27e50cb
     * 返回数据：
     * {
     *     "access_token": "2dcf6d4e-c0f7-4ac1-89ce-9dd2d27e50cb",
     *     "token_type": "bearer",
     *     "refresh_token": "2a9a4d47-8d03-46df-9a14-74f318489774",
     *     "expires_in": 3599,
     *     "scope": "read"
     * }
     *简化模式:
     *http://localhost:9000/oauth/authorize?client_id=client&response_type=token&scope=read&redirect_uri=http://www.baidu.com
     *返回数据和授权码模式返回的token一样
     *
     * 密码模式：
     * http://localhost:9000/oauth/token?username=bml&password=123456&grant_type=password&client_id=client&client_secret=111111&scope=read
     *
     * 客户端模式：
     *http://localhost:9000/oauth/token?grant_type=client_credentials&client_id=client&client_secret=111111&scope=read
     * 刷新token：
     * http://localhost:9000/oauth/token?grant_type=refresh_token&client_id=client&client_secret=111111&refresh_token=dfde953f-76dd-4a21-baa1-e91676967edd
     *
     */

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client").secret(passwordEncoder.encode("111111"))
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(864000)
                .redirectUris("http://www.baidu.com")
                .scopes("read")
                //配置授权的类型：
                // authorization_code:授权吗模式
                //refresh_token:刷新token
                //password:密码模式
                //implicait：简化模式
                //client_credentials:客户端模式
                .authorizedGrantTypes("authorization_code","refresh_token","password","implicit","client_credentials");
    }



    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许表单验证
     security.authenticationEntryPoint(authenticationEntryPoint())
             .allowFormAuthenticationForClients();
    }
    /**
     * 服务端端点配置
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        DefaultAccessTokenConverter defaultAccessTokenConverter = new DefaultAccessTokenConverter();
        defaultAccessTokenConverter.setUserTokenConverter(unifiedDefaultUserAuthenticationConverter);

        //使用密码模式需要配置验证管理器
        endpoints.authenticationManager(authenticationManager)
                .redirectResolver(customizationRedirectResolver)
                .exceptionTranslator(new AuthServerExceptionHandler())
                .accessTokenConverter(defaultAccessTokenConverter)
                .userDetailsService(userService)//刷新令牌的时候需要重新验证用户身份
                //允许HTTP GET 和POST
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);

    }
    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, authException) -> {
            Result<String> result = Result.ofFail(9999, "erros");
            writeJsonToResponse(response, result.buildResultJson());
        };
    }
    public static void writeJsonToResponse(HttpServletResponse response, String jsonString) {
        byte[] bytes;
        try {
            bytes = jsonString.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        response.setContentType("application/json; charset=utf-8");
        response.setContentLength(bytes.length);
        try (ServletOutputStream servletOutputStream = response.getOutputStream()) {
            servletOutputStream.write(bytes);
            servletOutputStream.flush();
        } catch (Exception ce) {
            throw new RuntimeException(ce);
        }
    }
}
